#include <stdio.h>
#include <stdint.h>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Walloc-size-larger-than="

#define RINGBUF_IMPLEMENTATION
#include "ringbuf.h"

static void prefix_function(void);
static void postfix_function(void);

#define TEST_INIT prefix_function()
#define TEST_FINI postfix_function()
#include "acutest.h"

#ifndef UNUSED
#define UNUSED(X) ((void)X)
#endif

#define ITEMS         100
#define MAX_ITEM_SIZE 11

static char const *string = "Hello, world!";
static void *ring;
static char bigbuffer[ITEMS * MAX_ITEM_SIZE];
static char bigbuffer2[sizeof(bigbuffer)];
static void* buffer_in_use = NULL;
static void* buffer_in_use2 = NULL;
static int error_count;

static void my_error_handler(const char* f, int l, const char* msg) {
    fprintf(stdout, "\nError Handled: %s(%d): %s\t", f, l, msg);
    error_count++;
}

static void prefix_function(void)
{
    error_count = 0;
    ringbuf_error_handler = my_error_handler;
    memset(bigbuffer, 0, sizeof(bigbuffer));
    memset(bigbuffer2, 0, sizeof(bigbuffer2));
    ring = ringbuf_create(buffer_in_use, 1, ITEMS);
}

static void postfix_function(void)
{
    ringbuf_destroy(ring);
}


static void test_setup_dynamic(void)
{
    buffer_in_use = buffer_in_use2 = NULL;
    TEST_CHECK(!buffer_in_use);
    TEST_CHECK(!buffer_in_use2);
}

static void test_setup_static(void)
{
    buffer_in_use = bigbuffer;
    buffer_in_use2 = bigbuffer2;
    TEST_CHECK(buffer_in_use);
    TEST_CHECK(buffer_in_use2);
}

// test that input and output sizes match with standard insert/remove
static void test_regular_insert_remove_match(void)
{
    size_t i, o;

    TEST_CHECK(ringbuf_isempty(ring));

    for (i = 0; string[i]; i++)
    {
        ringbuf_insert(ring, &string[i]);
        TEST_CHECK(!ringbuf_isempty(ring));
    }
    TEST_CHECK(ringbuf_count(ring) == i);
    TEST_MSG("expected count of %lu, got count of %lu", i, ringbuf_count(ring));

    for (o = 0; !ringbuf_isempty(ring); o++)
    {
        ringbuf_remove(ring);
    }
    TEST_CHECK(ringbuf_isempty(ring));
    TEST_MSG("expected empty ringbuf, got count of %lu", ringbuf_count(ring));

    TEST_CHECK(i == o);
}

// test that input and output sizes match with reverse insert/remove
static void test_reverse_insert_remove_match(void)
{
    size_t i, o;

    TEST_CHECK(ringbuf_isempty(ring));

    for (i = 0; string[i]; i++)
    {
        ringbuf_binsert(ring, &string[i]);
        TEST_CHECK(!ringbuf_isempty(ring));
    }
    TEST_CHECK(ringbuf_count(ring) == i);
    TEST_MSG("expected count of %lu, got count of %lu", i, ringbuf_count(ring));

    for (o = 0; !ringbuf_isempty(ring); o++)
    {
        ringbuf_fremove(ring);
    }
    TEST_CHECK(ringbuf_isempty(ring));
    TEST_MSG("expected empty ringbuf, got count of %lu", ringbuf_count(ring));

    TEST_CHECK(i == o);
}

// test that input and output sizes match with lifo front
static void test_front_lifo_insert_remove_match(void)
{
    size_t i, o;

    TEST_CHECK(ringbuf_isempty(ring));

    for (i = 0; string[i]; i++)
    {
        ringbuf_finsert(ring, &string[i]);
        TEST_CHECK(!ringbuf_isempty(ring));
    }
    TEST_CHECK(ringbuf_count(ring) == i);
    TEST_MSG("expected count of %lu, got count of %lu", i, ringbuf_count(ring));

    for (o = 0; !ringbuf_isempty(ring); o++)
    {
        ringbuf_fremove(ring);
    }
    TEST_CHECK(ringbuf_isempty(ring));
    TEST_MSG("expected empty ringbuf, got count of %lu", ringbuf_count(ring));

    TEST_CHECK(i == o);
}

// test that input and output sizes match with lifo back
static void test_back_lifo_insert_remove_match(void)
{
    size_t i, o;

    TEST_CHECK(ringbuf_isempty(ring));

    for (i = 0; string[i]; i++)
    {
        ringbuf_binsert(ring, &string[i]);
        TEST_CHECK(!ringbuf_isempty(ring));
    }
    TEST_CHECK(ringbuf_count(ring) == i);
    TEST_MSG("expected count of %lu, got count of %lu", i, ringbuf_count(ring));

    for (o = 0; !ringbuf_isempty(ring); o++)
    {
        ringbuf_bremove(ring);
    }
    TEST_CHECK(ringbuf_isempty(ring));
    TEST_MSG("expected empty ringbuf, got count of %lu", ringbuf_count(ring));

    TEST_CHECK(i == o);
}

// test that this is a ring buffer
static void test_actually_a_ring(void)
{
    size_t i, j;

    TEST_CHECK(ringbuf_isempty(ring));

    static const char c = '1';
    size_t reps = ringbuf_maxitems(ring);

    for (i = 0; i < reps; i++)
    {
        ringbuf_insert(ring, &c);
        TEST_CHECK(!ringbuf_isempty(ring));
    }
    TEST_CHECK(ringbuf_count(ring) == i);
    TEST_CHECK(ringbuf_count(ring) == ringbuf_maxitems(ring));
    TEST_MSG("expected count of %lu, got count of %lu", i, ringbuf_count(ring));

    static const char d = '2';
    reps >>= 1;
    for (j = 0; j < reps; j++)
    {
        ringbuf_insert(ring, &d);
        TEST_CHECK(!ringbuf_isempty(ring));
    }
    TEST_CHECK(ringbuf_count(ring) != i + j);
    TEST_CHECK(ringbuf_count(ring) == i);
    TEST_CHECK(ringbuf_count(ring) == ringbuf_maxitems(ring));
}

// test that regrowing works, converting to self-managed
static void test_self_managed_resize(void)
{
    RingBuffer *r = (RingBuffer *)ring;
    volatile const uint8_t *buffer_orig = r->buffer;
    TEST_CHECK(r->self_managed == (buffer_in_use ? false : true));

    size_t reps = ringbuf_maxitems(ring);
    ringbuf_resize(ring, NULL, reps + 15);
    TEST_CHECK(ringbuf_maxitems(ring) != reps);
    TEST_CHECK(ringbuf_maxitems(ring) == reps + 15);
    TEST_MSG("expected maxitems of %lu, got maxitems of %lu", reps + 15, ringbuf_maxitems(ring));

    volatile const uint8_t *buffer_new = r->buffer;
    TEST_CHECK(buffer_new != buffer_orig);
    TEST_CHECK(r->self_managed == true);
}

// test that regrowing works, converting to external-managed
static void test_ext_managed_resize(void)
{
    RingBuffer *r = (RingBuffer *)ring;
    volatile const uint8_t *buffer_orig = r->buffer;
    TEST_CHECK(r->self_managed == (buffer_in_use ? false : true));

    size_t reps = ringbuf_maxitems(ring);
    uint8_t *buffer = malloc(reps + 15);
    ringbuf_resize(ring, buffer, reps + 15);
    TEST_CHECK(ringbuf_maxitems(ring) != reps);
    TEST_CHECK(ringbuf_maxitems(ring) == reps + 15);
    TEST_MSG("expected maxitems of %lu, got maxitems of %lu", reps + 15, ringbuf_maxitems(ring));

    volatile const uint8_t *buffer_new = r->buffer;
    TEST_CHECK(buffer_new != buffer_orig);
    TEST_CHECK(buffer_new == buffer);
    TEST_CHECK(r->self_managed == false);
    free(buffer);
}

// test clone to self-managed
static void test_clone_self_managed(void)
{
    RingBuffer *r = (RingBuffer *)ring;
    RingBuffer *r2 = (RingBuffer *)ringbuf_clone(ring, NULL);
    TEST_CHECK(r2->self_managed);
    TEST_CHECK(r->empty == r2->empty);
    TEST_CHECK(r->front == r2->front);
    TEST_CHECK(r->back == r2->back);
    TEST_CHECK(r->max_items == r2->max_items);
    TEST_CHECK(r->item_size == r2->item_size);
    TEST_CHECK(r->buffer != r2->buffer);
    TEST_CHECK(!memcmp((void *)r->buffer, (void *)r2->buffer, r->item_size * r->max_items));
    ringbuf_destroy(r2);
}

// test clone to external-managed
static void test_clone_ext_managed(void)
{
    RingBuffer *r = (RingBuffer *)ring;
    uint8_t *buffer = malloc(ringbuf_maxitems(ring));
    RingBuffer *r2 = (RingBuffer *)ringbuf_clone(ring, buffer);
    TEST_CHECK(!r2->self_managed);
    TEST_CHECK(r->empty == r2->empty);
    TEST_CHECK(r->front == r2->front);
    TEST_CHECK(r->back == r2->back);
    TEST_CHECK(r->max_items == r2->max_items);
    TEST_CHECK(r->item_size == r2->item_size);
    TEST_CHECK(r->buffer != r2->buffer);
    TEST_CHECK(r2->buffer == buffer);
    ringbuf_destroy(r2);
    free(buffer);
}

// test error handling on malloc flaws
static void test_error_handler(void)
{
    const static int invalid_size = -1;
    TEST_CHECK(!ringbuf_create(NULL, 1, invalid_size));
    TEST_CHECK(error_count == 1);
    TEST_CHECK(!ringbuf_resize(ring, NULL, invalid_size));
    TEST_CHECK(error_count == 2);
}

TEST_LIST =
{
    { "SET UP DYNAMIC RINGBUF TESTS", test_setup_dynamic                  },
    { "input/output match",           test_regular_insert_remove_match    },
    { "reverse input/output match",   test_reverse_insert_remove_match    },
    { "forward lifo match",           test_front_lifo_insert_remove_match },
    { "reverse lifo match",           test_back_lifo_insert_remove_match  },
    { "actually a ring buffer",       test_actually_a_ring                },
    { "self-managed resizing",        test_self_managed_resize            },
    { "external-managed resizing",    test_ext_managed_resize             },
    { "clone into self-managed",      test_clone_self_managed             },
    { "clone into external-managed",  test_clone_ext_managed              },
    { "memory error handling",        test_error_handler                  },

    { "SET UP STATIC RINGBUF TESTS",  test_setup_static                   },
    { "input/output match",           test_regular_insert_remove_match    },
    { "reverse input/output match",   test_reverse_insert_remove_match    },
    { "forward lifo match",           test_front_lifo_insert_remove_match },
    { "reverse lifo match",           test_back_lifo_insert_remove_match  },
    { "actually a ring buffer",       test_actually_a_ring                },
    { "self-managed resizing",        test_self_managed_resize            },
    { "external-managed resizing",    test_ext_managed_resize             },
    { "clone into self-managed",      test_clone_self_managed             },
    { "clone into external-managed",  test_clone_ext_managed              },
    { "memory error handling",        test_error_handler                  },

    { NULL, NULL },
};

#pragma GCC diagnostic pop
